package com.cksys.achievement.config.login.qq;

import com.alibaba.fastjson.JSON;
import com.cksys.achievement.exception.MyUsernameNotFoundException;
import com.cksys.achievement.exception.NoBindingException;
import lombok.Data;
import org.jsoup.Jsoup;
import org.jsoup.nodes.Document;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.web.authentication.AbstractAuthenticationProcessingFilter;
import org.springframework.security.web.util.matcher.RequestMatcher;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

/**
 * @ClassName: QQAuthenticationFilter
 * @author: zyx
 * @E-mail: 1377631190@qq.com
 * @DATE: 2020/4/16 13:39
 */
public class QQAuthenticationFilter extends AbstractAuthenticationProcessingFilter {

    private String clientId;
    private String clientSecret;
    private String redirectUri;

    public QQAuthenticationFilter(RequestMatcher requiresAuthenticationRequestMatcher, String clientId, String clientSecret, String redirectUri) {
        super(requiresAuthenticationRequestMatcher);
        this.clientId = clientId;
        this.clientSecret = clientSecret;
        this.redirectUri = redirectUri;
    }

    @Override
    public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException, IOException {
        String code = request.getParameter(CODE);
        String tokenAccessApi = String.format(TOKEN_ACCESS_API, accessTokenUri, grantType, clientId,
                clientSecret, code, redirectUri);
        QQToken qqToken = this.getToken(tokenAccessApi);
        if (qqToken != null) {
            String unionId = getUnionId(qqToken.getAccessToken());
            if (unionId != null) {
                UsernamePasswordAuthenticationToken authenticationToken =
                        new UsernamePasswordAuthenticationToken(qqToken.getAccessToken(), unionId);
                Authentication authentication = getAuthenticationManager().authenticate(authenticationToken);
                if (authentication == null) {
                    request.getSession().setAttribute("unionId", unionId);
                    throw new NoBindingException(unionId + "未绑定");
                }
                return authentication;
            }
        }
        throw new MyUsernameNotFoundException("获取失败！");
    }

    private final static String CODE = "code";
    private final static String grantType = "authorization_code";

    private final static String accessTokenUri = "https://graph.qq.com/oauth2.0/token";
    private final static String openIdUri = "https://graph.qq.com/oauth2.0/me?access_token=";
    private final static String UNION_ID = "https://graph.qq.com/oauth2.0/me?access_token=%s&unionid=1&fmt=json";

    private static Pattern p = Pattern.compile("\"openid\":\"(.*?)\"");

    /**
     * 获取 token 的地址拼接
     */
    private final static String TOKEN_ACCESS_API = "%s?grant_type=%s&client_id=%s&client_secret=%s&code=%s&redirect_uri=%s";

    private QQToken getToken(String tokenAccessApi) throws IOException {
        Document document = Jsoup.connect(tokenAccessApi)
                .timeout(30000)
                .userAgent("UA")
                .validateTLSCertificates(false).get();
        String tokenResult = document.text();
        String[] results = tokenResult.split("&");
        if (results.length == 3) {
            QQToken qqToken = new QQToken();
            String accessToken = results[0].replace("access_token=", "");
            int expiresIn = Integer.parseInt(results[1].replace("expires_in=", ""));
            String refreshToken = results[2].replace("refresh_token=", "");
            qqToken.setAccessToken(accessToken);
            qqToken.setExpiresIn(expiresIn);
            qqToken.setRefresh_token(refreshToken);
            return qqToken;
        }
        return null;
    }

    /**
     * <p><b>推荐使用{@code getUnionId}方法</b><p/>
     *
     * @param accessToken   用户登录token
     * @return              openId
     */
    @Deprecated
    private String getOpenId(String accessToken) throws IOException {
        String url = openIdUri + accessToken;
        Document document = Jsoup.connect(url)
                .timeout(30000)
                .userAgent("UA")
                .validateTLSCertificates(false).get();
        String resultText = document.text();
        Matcher matcher = pattern(resultText);
        if (matcher.find()) {
            return matcher.group(1);
        }
        return null;
    }

    /**
     * <p><b>获取全局唯一id</b></p>
     * openId存在不同设备登录时，不唯一的情况<br/>
     * 如手机端和PC端登录openId不同
     *
     * @param accessToken   用户登录token
     * @return              unionId
     * @throws IOException  IO异常
     */
    private String getUnionId(String accessToken) throws IOException {
        String url = String.format(UNION_ID, accessToken);
        Document document = Jsoup.connect(url)
                .timeout(30000)
                .userAgent("UA")
                .validateTLSCertificates(false).get();
        String resultText = document.text();
        return String.valueOf(JSON.parseObject(resultText).get("unionid"));
    }

    @Data
    class QQToken {

        /**
         * token
         */
        private String accessToken;

        /**
         * 有效期
         */
        private int expiresIn;

        /**
         * 刷新时用的 token
         */
        private String refresh_token;
    }

    public static Matcher pattern(String str){
        Matcher m=p.matcher(str);
        return m;
    }
}
